{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "https://python.langchain.com/docs/how_to/tools_human/\n",
    "\n",
    "创建工具及工具调用链"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "7d1967b7705e4d86"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "\n",
    "\n",
    "@tool\n",
    "def count_emails(last_n_days: int) -> int:\n",
    "    \"\"\"Dummy function to count number of e-mails. Returns 2 * last_n_days.\"\"\"\n",
    "    return last_n_days * 2\n",
    "\n",
    "\n",
    "@tool\n",
    "def send_email(message: str, recipient: str) -> str:\n",
    "    \"\"\"Dummy function for sending an e-mail.\"\"\"\n",
    "    return f\"Successfully sent email to {recipient}.\"\n",
    "\n",
    "tools = [count_emails, send_email]"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-31T05:37:06.267461Z",
     "start_time": "2024-10-31T05:37:06.249061Z"
    }
   },
   "id": "eea9f29a496af2ae",
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "text/plain": "[{'name': 'count_emails',\n  'args': {'last_n_days': 5},\n  'id': 'call_bdbdb1eaac9a40e0bc3c2c',\n  'type': 'tool_call',\n  'output': 10}]"
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "from typing import List, Dict\n",
    "from langchain_core.messages import AIMessage\n",
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()\n",
    "llm = ChatOpenAI(\n",
    "    # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "    openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "    model_name=\"qwen-max\",\n",
    "    max_retries=0,\n",
    ")\n",
    "\n",
    "llm_with_tools = llm.bind_tools(tools)\n",
    "\n",
    "def call_tools(msg: AIMessage) -> List[Dict]:\n",
    "    \"\"\"Simple sequential tool calling helper.\"\"\"\n",
    "    tool_map = {tool.name: tool for tool in tools}\n",
    "    tool_calls = msg.tool_calls.copy()\n",
    "    for tool_call in tool_calls:\n",
    "        tool_call[\"output\"] = tool_map[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n",
    "    return tool_calls\n",
    "\n",
    "chain = llm_with_tools | call_tools\n",
    "chain.invoke(\"how many emails did i get in the last 5 days?\")\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-31T05:38:01.905999Z",
     "start_time": "2024-10-31T05:37:59.613408Z"
    }
   },
   "id": "c2473e05e95aa7f7",
   "execution_count": 4
  },
  {
   "cell_type": "markdown",
   "source": [
    "##  添加人工审核\n",
    "让我们在链中添加一个步骤，该步骤将询问人员是否批准或拒绝高通话请求。\n",
    "在拒绝的情况下，该步骤将引发一个异常，这将停止链中其余部分的执行。\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "fc98cd2bf7380dc9"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "class NotApproved(Exception):\n",
    "    \"\"\"Custom exception.\"\"\"\n",
    "\n",
    "def human_approval(msg: AIMessage) -> AIMessage:\n",
    "    \"\"\"Responsible for passing through its input or raising an exception.\n",
    "\n",
    "    Args:\n",
    "        msg: output from the chat model\n",
    "\n",
    "    Returns:\n",
    "        msg: original output from the msg\n",
    "    \"\"\"\n",
    "    tool_strs = \"\\n\\n\".join(\n",
    "        json.dumps(tool_call, indent=2) for tool_call in msg.tool_calls\n",
    "    )\n",
    "    input_msg = (\n",
    "        f\"Do you approve of the following tool invocations\\n\\n{tool_strs}\\n\\n\"\n",
    "        \"Anything except 'Y'/'Yes' (case-insensitive) will be treated as a no.\\n >>>\"\n",
    "    )\n",
    "    resp = input(input_msg)\n",
    "    if resp.lower() not in (\"yes\", \"y\"):\n",
    "        raise NotApproved(f\"Tool invocations not approved:\\n\\n{tool_strs}\")\n",
    "    return msg"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-31T05:41:54.515746Z",
     "start_time": "2024-10-31T05:41:54.509732Z"
    }
   },
   "id": "4744eaf45432e330",
   "execution_count": 7
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "ename": "NotApproved",
     "evalue": "Tool invocations not approved:\n\n{\n  \"name\": \"count_emails\",\n  \"args\": {\n    \"last_n_days\": 5\n  },\n  \"id\": \"call_703c10c5899040e7bc2feb\",\n  \"type\": \"tool_call\"\n}",
     "output_type": "error",
     "traceback": [
      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[0;31mNotApproved\u001B[0m                               Traceback (most recent call last)",
      "Cell \u001B[0;32mIn[9], line 2\u001B[0m\n\u001B[1;32m      1\u001B[0m chain \u001B[38;5;241m=\u001B[39m llm_with_tools \u001B[38;5;241m|\u001B[39m human_approval \u001B[38;5;241m|\u001B[39m call_tools\n\u001B[0;32m----> 2\u001B[0m chain\u001B[38;5;241m.\u001B[39minvoke(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mhow many emails did i get in the last 5 days?\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langchain_core/runnables/base.py:3024\u001B[0m, in \u001B[0;36mRunnableSequence.invoke\u001B[0;34m(self, input, config, **kwargs)\u001B[0m\n\u001B[1;32m   3022\u001B[0m             \u001B[38;5;28minput\u001B[39m \u001B[38;5;241m=\u001B[39m context\u001B[38;5;241m.\u001B[39mrun(step\u001B[38;5;241m.\u001B[39minvoke, \u001B[38;5;28minput\u001B[39m, config, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)\n\u001B[1;32m   3023\u001B[0m         \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m-> 3024\u001B[0m             \u001B[38;5;28minput\u001B[39m \u001B[38;5;241m=\u001B[39m context\u001B[38;5;241m.\u001B[39mrun(step\u001B[38;5;241m.\u001B[39minvoke, \u001B[38;5;28minput\u001B[39m, config)\n\u001B[1;32m   3025\u001B[0m \u001B[38;5;66;03m# finish the root run\u001B[39;00m\n\u001B[1;32m   3026\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mBaseException\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m e:\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langchain_core/runnables/base.py:4713\u001B[0m, in \u001B[0;36mRunnableLambda.invoke\u001B[0;34m(self, input, config, **kwargs)\u001B[0m\n\u001B[1;32m   4699\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Invoke this Runnable synchronously.\u001B[39;00m\n\u001B[1;32m   4700\u001B[0m \n\u001B[1;32m   4701\u001B[0m \u001B[38;5;124;03mArgs:\u001B[39;00m\n\u001B[0;32m   (...)\u001B[0m\n\u001B[1;32m   4710\u001B[0m \u001B[38;5;124;03m    TypeError: If the Runnable is a coroutine function.\u001B[39;00m\n\u001B[1;32m   4711\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m   4712\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mhasattr\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mfunc\u001B[39m\u001B[38;5;124m\"\u001B[39m):\n\u001B[0;32m-> 4713\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_call_with_config(\n\u001B[1;32m   4714\u001B[0m         \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_invoke,\n\u001B[1;32m   4715\u001B[0m         \u001B[38;5;28minput\u001B[39m,\n\u001B[1;32m   4716\u001B[0m         \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_config(config, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mfunc),\n\u001B[1;32m   4717\u001B[0m         \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs,\n\u001B[1;32m   4718\u001B[0m     )\n\u001B[1;32m   4719\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m   4720\u001B[0m     msg \u001B[38;5;241m=\u001B[39m (\n\u001B[1;32m   4721\u001B[0m         \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mCannot invoke a coroutine function synchronously.\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m   4722\u001B[0m         \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mUse `ainvoke` instead.\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m   4723\u001B[0m     )\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langchain_core/runnables/base.py:1927\u001B[0m, in \u001B[0;36mRunnable._call_with_config\u001B[0;34m(self, func, input, config, run_type, serialized, **kwargs)\u001B[0m\n\u001B[1;32m   1923\u001B[0m     context \u001B[38;5;241m=\u001B[39m copy_context()\n\u001B[1;32m   1924\u001B[0m     context\u001B[38;5;241m.\u001B[39mrun(_set_config_context, child_config)\n\u001B[1;32m   1925\u001B[0m     output \u001B[38;5;241m=\u001B[39m cast(\n\u001B[1;32m   1926\u001B[0m         Output,\n\u001B[0;32m-> 1927\u001B[0m         context\u001B[38;5;241m.\u001B[39mrun(\n\u001B[1;32m   1928\u001B[0m             call_func_with_variable_args,  \u001B[38;5;66;03m# type: ignore[arg-type]\u001B[39;00m\n\u001B[1;32m   1929\u001B[0m             func,  \u001B[38;5;66;03m# type: ignore[arg-type]\u001B[39;00m\n\u001B[1;32m   1930\u001B[0m             \u001B[38;5;28minput\u001B[39m,  \u001B[38;5;66;03m# type: ignore[arg-type]\u001B[39;00m\n\u001B[1;32m   1931\u001B[0m             config,\n\u001B[1;32m   1932\u001B[0m             run_manager,\n\u001B[1;32m   1933\u001B[0m             \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs,\n\u001B[1;32m   1934\u001B[0m         ),\n\u001B[1;32m   1935\u001B[0m     )\n\u001B[1;32m   1936\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mBaseException\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m   1937\u001B[0m     run_manager\u001B[38;5;241m.\u001B[39mon_chain_error(e)\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langchain_core/runnables/config.py:396\u001B[0m, in \u001B[0;36mcall_func_with_variable_args\u001B[0;34m(func, input, config, run_manager, **kwargs)\u001B[0m\n\u001B[1;32m    394\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m run_manager \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m accepts_run_manager(func):\n\u001B[1;32m    395\u001B[0m     kwargs[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrun_manager\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m run_manager\n\u001B[0;32m--> 396\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m func(\u001B[38;5;28minput\u001B[39m, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langchain_core/runnables/base.py:4567\u001B[0m, in \u001B[0;36mRunnableLambda._invoke\u001B[0;34m(self, input, run_manager, config, **kwargs)\u001B[0m\n\u001B[1;32m   4565\u001B[0m                 output \u001B[38;5;241m=\u001B[39m chunk\n\u001B[1;32m   4566\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m-> 4567\u001B[0m     output \u001B[38;5;241m=\u001B[39m call_func_with_variable_args(\n\u001B[1;32m   4568\u001B[0m         \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mfunc, \u001B[38;5;28minput\u001B[39m, config, run_manager, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs\n\u001B[1;32m   4569\u001B[0m     )\n\u001B[1;32m   4570\u001B[0m \u001B[38;5;66;03m# If the output is a Runnable, invoke it\u001B[39;00m\n\u001B[1;32m   4571\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(output, Runnable):\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langchain_core/runnables/config.py:396\u001B[0m, in \u001B[0;36mcall_func_with_variable_args\u001B[0;34m(func, input, config, run_manager, **kwargs)\u001B[0m\n\u001B[1;32m    394\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m run_manager \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m accepts_run_manager(func):\n\u001B[1;32m    395\u001B[0m     kwargs[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrun_manager\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m run_manager\n\u001B[0;32m--> 396\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m func(\u001B[38;5;28minput\u001B[39m, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)\n",
      "Cell \u001B[0;32mIn[7], line 24\u001B[0m, in \u001B[0;36mhuman_approval\u001B[0;34m(msg)\u001B[0m\n\u001B[1;32m     22\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[38;5;28minput\u001B[39m(input_msg)\n\u001B[1;32m     23\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m resp\u001B[38;5;241m.\u001B[39mlower() \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;129;01min\u001B[39;00m (\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124myes\u001B[39m\u001B[38;5;124m\"\u001B[39m, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124my\u001B[39m\u001B[38;5;124m\"\u001B[39m):\n\u001B[0;32m---> 24\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m NotApproved(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mTool invocations not approved:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[38;5;132;01m{\u001B[39;00mtool_strs\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m     25\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m msg\n",
      "\u001B[0;31mNotApproved\u001B[0m: Tool invocations not approved:\n\n{\n  \"name\": \"count_emails\",\n  \"args\": {\n    \"last_n_days\": 5\n  },\n  \"id\": \"call_703c10c5899040e7bc2feb\",\n  \"type\": \"tool_call\"\n}"
     ]
    }
   ],
   "source": [
    "chain = llm_with_tools | human_approval | call_tools\n",
    "chain.invoke(\"how many emails did i get in the last 5 days?\")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-31T05:42:23.896343Z",
     "start_time": "2024-10-31T05:42:16.084853Z"
    }
   },
   "id": "4ef477e84a7fd325",
   "execution_count": 9
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
